[Ruby on Rails]sorceryによる認証 – (4)パスワードリセット
はじめに
前回に引き続きsorceryによる認証についてです。今回はパスワードリセットを実装してみました。
今回もsorceryで認証ができるアプリに機能を追加する形で実装していきます。またパスワードリセット時にメールを送信するため、ActionMailerの設定も必要です。予め以下の記事を参考にemail・passwordでアカウントを登録し、ActionMailerでメールを送信できるアプリを作成しておいてください。
公式チュートリアル(Simple-Password-Authentication)
公式チュートリアル(User-Activation)
以前の記事
パスワードリセットの画面フロー
今回作成する機能のイメージを掴みやすくするため、画面のスクリーンショットを遷移順に貼っておきます。
1.ログイン画面
ログイン画面の下部にパスワードを忘れた場合にメールアドレスを入力する欄があります。ここにメールアドレスを入力して「Reset my password!」を押します。
2.メール送信
画面
メール
画面にはメールを送信した旨のメッセージが表示されます。送られてくるメールにはパスワードリセット画面のURLが書かれています。
3.パスワードリセット画面
メールに書かれたURLにアクセスすると、新しいパスワードを設定する画面が表示されます。パスワードを入力し「Update User」ボタンを押します。
4.パスワードリセット完了
パスワードがリセットされ、完了した旨のメッセージが表示されます。
アプリケーションの実装
では実装についてです。今回も公式チュートリアルをなぞりましたが、幾つか追加したり変更した手順もありますので合わせて紹介したいと思います。
1.「reset_password」サブモジュールのインストール
パスワードリセットを行うためのサブモジュールをインストールします。以下のコマンドを実行してください。
$ rails g sorcery:install reset_password --migrations
以下のようなマイグレーションファイルが作成されます。
class SorceryResetPassword < ActiveRecord::Migration def change add_column :users, :reset_password_token, :string, :default => nil add_column :users, :reset_password_token_expires_at, :datetime, :default => nil add_column :users, :reset_password_email_sent_at, :datetime, :default => nil end end
マイグレーションを実行してDBに反映します。
$ rake db:migrate
またsorceryの定義ファイルに、使用するサブモジュールとして「reset_password」が追加されていることを確認してください(無ければ追加してください)。
config/initializers/sorcery.rb
Rails.application.config.sorcery.submodules = [:reset_password, ・・・]
2.ActionMailerの定義
ActionMailerの定義を行います。まず以下のコマンドを実行してください。
$ rails g mailer UserMailer reset_password_email
前回の続きから行っている場合など既に「UserMailer」が作成済みの場合は、上書きするかを選択するメッセージが表示されるので注意してください。今回は実装済みの機能を壊したくなかったなめ、上書きしないようにしました。
sorceryの定義ファイルにパスワードリセットに使用するActionMailerとしてUserMailerを定義します。
config/initializers/sorcery.rb
config.user_config do |user| (中略) user.reset_password_mailer = UserMailer (中略) end
パスワードリセット時のメール本文を定義します。
app/views/user_mailer/reset_password_email.text.erb
Hello, <%= @user.email %> =============================================== You have requested to reset your password. To choose a new password, just follow this link: <%= @url %>. Have a great day!
テキスト形式のメールのみ定義したため「app/views/user_mailer」配下の「〜.html.erb」は削除します。
アクションを実装します。
app/mailers/user_mailer.rb
def reset_password_email(user) @user = User.find user.id @url = "http://0.0.0.0:3000" + edit_password_reset_path(@user.reset_password_token) mail(:to => user.email, :subject => "Your password has been reset") end
チュートリアルではURLの取得に「edit_password_reset_url」を使用していましたが、Rails4では以下のエラーとなってしまいました。
Missing host to link to!
このため使用するメソッドを「edit_password_reset_path」に変更しています。またメールに「http」を含むURLの形式で出力するよう「"http://0.0.0.0:3000"」と連結しています。
これはsorceryの履歴の記述を参考にしました。
When use Rails4, an error occurs "Missing host to link to! Please provide the :host parameter". so I change 'edit_password_reset_url' to 'edit_password_reset_path'
3.パスワードリセットを行うコントローラを定義
パスワードリセットを行うコントローラを作成します。
$ rails g controller PasswordResets create edit update
作成されたコントローラを以下のように編集します。
app/controllers/password_resets_controller.rb
class PasswordResetsController < ApplicationController skip_before_filter :require_login def create @user = User.find_by_email(params[:email]) @user.deliver_reset_password_instructions! if @user redirect_to(root_path, :notice => 'Instructions have been sent to your email.') end def edit set_token_user_from_params? end def update return if !set_token_user_from_params? @user.password_confirmation = params[:user][:password_confirmation] if @user.change_password!(params[:user][:password]) redirect_to(root_path, :notice => 'Password was successfully updated.') else render :action => "edit" end end private def set_token_user_from_params? @token = params[:id] @user = User.load_from_reset_password_token(params[:id]) if @user.blank? not_authenticated return false else return true end end end
チュートリアルでは「edit」「update」のそれぞれで
- Postされてくる値とユーザオブジェクトを取得する
- ユーザオブジェクトの存在チェックを行う
を行っていますが、今回はこれらの処理を「set_token_user_from_params?」に纏めています。
作成したコントローラをルーティングに追加します。
config/routes.rb
resources :password_resets
同時に以下をルーティングから削除します。
get 'password_resets/create' get 'password_resets/edit' get 'password_resets/update'
4.画面の作成
パスワードをリセットするためのフォームを作成します。
app/views/password_resets/edit.html.erb
<h1>Choose a new password</h1> <%= form_for @user, :url => password_reset_path(@token), :html => {:method => :put} do |f| %> <% if @user.errors.any? %> <div id="error_explanation"> <h2 id="toc-prohibited-this-user-from-being-saved"><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2> <ul> <% @user.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %> <div class="field"> <%= f.label :email %><br /> <%= @user.email %> </div> <div class="field"> <%= f.label :password %><br /> <%= f.password_field :password %> </div> <div class="field"> <%= f.label :password_confirmation %><br /> <%= f.password_field :password_confirmation %> </div> <div class="actions"> <%= f.submit %> </div> <% end %>
パスワードを忘れた場合にメールアドレスを入力するフォームを、ログイン画面に追加します。
app/views/user_sessions/new.html.erb
(中略) <h1>Forgot Password?</h1> <%= render 'forgot_password_form' %>
テンプレート(forgot_password_form)を呼び出しているので、このテンプレートを定義します。
app/views/user_sessions/_forgot_password_form.html.erb
<%= form_tag password_resets_path, :method => :post do %> <div class="field"> <%= label_tag :email %><br /> <%= text_field_tag :email %> <%= submit_tag "Reset my password!" %> </div> <% end %>
まとめ
以上でパスワードリセット機能を実装することができました。前回と同じく、非常の簡単に実装できました。
参考サイト
以下のサイトを参考にさせて頂きました。ありがとうございました。
公式チュートリアル(Reset password)
sorceryの履歴